#!/usr/bin/env python
#
# Copyright 2005,2006 Free Software Foundation, Inc.
# 
# This file is part of GNU Radio
# 
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
# 
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
# 


# /////////////////////////////////////////////////////////////////////////////
#
#    This code sets up up a virtual ethernet interface (typically gr0),
#    and relays packets between the interface and the GNU Radio PHY+MAC
#
#    What this means in plain language, is that if you've got a couple
#    of USRPs on different machines, and if you run this code on those
#    machines, you can talk between them using normal TCP/IP networking.
#
# /////////////////////////////////////////////////////////////////////////////


from gnuradio import gr, gru, modulation_utils, blks
from gnuradio import usrp
from gnuradio import eng_notation
from gnuradio import audio
from gnuradio.eng_option import eng_option
from optparse import OptionParser
from gnuradio.vocoder import gsm_full_rate
from gnuradio.vocoder import lpc

import random
import time
import struct
import sys
import os
import pkt
import select

# from current dir
from transmit_path import transmit_path
from receive_path import receive_path
import fusb_options

AUDIO_FRAME_SIZE = 7
AUDIO_FRAMES_PER_PACKAGE = 6

class audio_rx(gr.hier_block):
    def __init__(self, fg, audio_input_dev):
        sample_rate = 8000
        src = audio.source(sample_rate, audio_input_dev)
        src_scale = gr.multiply_const_ff(32767)
        f2s = gr.float_to_short()
        voice_coder = lpc.lpc10_encoder()
        self.packets_from_encoder = gr.msg_queue()
        packet_sink = gr.message_sink(AUDIO_FRAME_SIZE, self.packets_from_encoder, False)
        fg.connect(src, src_scale, f2s, voice_coder, packet_sink)
        gr.hier_block.__init__(self, fg, src, packet_sink)
	self.out_queue = []

    def get_encoded_voice_packet(self):
	if len(self.out_queue) != 0:
	    return self.out_queue.pop(0)

        data = self.packets_from_encoder.delete_head().to_string()
	for x in range(0, len(data), AUDIO_FRAME_SIZE):
	    self.out_queue.append(data[x:x+AUDIO_FRAME_SIZE])

	return self.out_queue.pop(0)
        
class audio_tx(gr.hier_block):
    def __init__(self, fg, audio_output_dev):
        self.packet_src = gr.message_source(AUDIO_FRAME_SIZE)
        voice_decoder = lpc.lpc10_decoder()
        s2f = gr.short_to_float ()
        sink_scale = gr.multiply_const_ff(1.0/32767.)
        audio_sink = audio.sink(8000, audio_output_dev)
        fg.connect(self.packet_src, voice_decoder, s2f, sink_scale, audio_sink)
        gr.hier_block.__init__(self, fg, self.packet_src, audio_sink)
        
    def msgq(self):
        return self.packet_src.msgq()

# /////////////////////////////////////////////////////////////////////////////
#                             the flow graph
# /////////////////////////////////////////////////////////////////////////////

class my_graph(gr.flow_graph):

    def __init__(self, mod_class, demod_class,
                 rx_callback, options):

        gr.flow_graph.__init__(self)
        self.txpath = transmit_path(self, mod_class, options)
        self.rxpath = receive_path(self, demod_class, rx_callback, options)
        self.audio_rx = audio_rx(self, options.audio_input)
        self.audio_tx = audio_tx(self, options.audio_output)

    def send_pkt(self, payload):
        return self.txpath.send_pkt(payload)

    def carrier_sensed(self):
        """
        Return True if the receive path thinks there's carrier
        """
        return self.rxpath.carrier_sensed()


# /////////////////////////////////////////////////////////////////////////////
#                           Carrier Sense MAC
# /////////////////////////////////////////////////////////////////////////////

class cs_mac(object):
    """
    Prototype carrier sense MAC
    """
    def __init__(self, verbose=False):
        self.verbose = verbose
        self.fg = None             # flow graph (access to PHY)

    def set_flow_graph(self, fg):
        self.fg = fg

    def phy_rx_callback(self, ok, payload, type):
        """
        Invoked by thread associated with PHY to pass received packet up.

        @param ok: bool indicating whether payload CRC was OK
        @param payload: contents of the packet (string)
        """
        if not ok:
            print "sublayer says packet is invalid"

        if type == pkt.HEADER_TYPE:
	    print >>sys.stderr, "Incoming header!", payload
            import time
	    time.sleep(0.1) # Hack: Buffer 0.1s of incoming audio
        elif type == pkt.VOICE_TYPE:
	    for x in xrange(AUDIO_FRAMES_PER_PACKAGE):
		audio_payload = payload[:AUDIO_FRAME_SIZE]
		payload = payload[AUDIO_FRAME_SIZE:]
	        self.fg.audio_tx.msgq().insert_tail(gr.message_from_string(audio_payload))
	else:
	    print >>sys.stderr, "Unhandled packet type", type, repr(payload)

    def tx(self, payload):
        min_delay = 0.001               # seconds

        delay = min_delay
        while self.fg.carrier_sensed():
            sys.stderr.write('(blocked) ')
            time.sleep(delay)
            if delay < 0.050:
                delay = delay * 2       # exponential back-off

        self.fg.send_pkt(payload)



# /////////////////////////////////////////////////////////////////////////////
#                                   main
# /////////////////////////////////////////////////////////////////////////////

def main():

    mods = modulation_utils.type_1_mods()
    demods = modulation_utils.type_1_demods()

    parser = OptionParser (option_class=eng_option, conflict_handler="resolve")
    expert_grp = parser.add_option_group("Expert")

    parser.add_option("-m", "--modulation", type="choice", choices=mods.keys(),
                      default='gmsk',
                      help="Select modulation from: %s [default=%%default]"
                            % (', '.join(mods.keys()),))
    parser.add_option("-I", "--audio-input", type="string", default="plughw:0",
                      help="pcm input device name.  E.g., hw:0,0 or /dev/dsp")

    parser.add_option("-O", "--audio-output", type="string", default="plughw:0",
                      help="pcm output device name.  E.g., hw:0,0 or /dev/dsp")

    parser.add_option("-v","--verbose", action="store_true", default=False)
    parser.add_option("-R","--realtime", action="store_true", default=False)
    expert_grp.add_option("-c", "--carrier-threshold", type="eng_float", default=30,
                          help="set carrier detect threshold (dB) [default=%default]")

    transmit_path.add_options(parser, expert_grp)
    receive_path.add_options(parser, expert_grp)

    for mod in mods.values():
        mod.add_options(expert_grp)

    for demod in demods.values():
        demod.add_options(expert_grp)

    fusb_options.add_options(expert_grp)

    (options, args) = parser.parse_args ()
    if len(args) != 0:
        parser.print_help(sys.stderr)
        sys.exit(1)

    if options.rx_freq is None or options.tx_freq is None:
        sys.stderr.write("You must specify -f FREQ or --freq FREQ\n")
        parser.print_help(sys.stderr)
        sys.exit(1)

    # Attempt to enable realtime scheduling
    if options.realtime and gr.RT_OK == gr.enable_realtime_scheduling():
        realtime = True
    else:
        realtime = False

    # If the user hasn't set the fusb_* parameters on the command line,
    # pick some values that will reduce latency.

    if options.fusb_block_size == 0 and options.fusb_nblocks == 0:
        if realtime:                        # be more aggressive
            options.fusb_block_size = gr.prefs().get_long('fusb', 'rt_block_size', 1024)
            options.fusb_nblocks    = gr.prefs().get_long('fusb', 'rt_nblocks', 16)
        else:
            options.fusb_block_size = gr.prefs().get_long('fusb', 'block_size', 4096)
            options.fusb_nblocks    = gr.prefs().get_long('fusb', 'nblocks', 16)
    
    #print "fusb_block_size =", options.fusb_block_size
    #print "fusb_nblocks    =", options.fusb_nblocks

    # instantiate the MAC
    mac = cs_mac(verbose=True)

    # build the graph (PHY)
    fg = my_graph(mods[options.modulation],
                  demods[options.modulation],
                  mac.phy_rx_callback,
                  options)

    mac.set_flow_graph(fg)    # give the MAC a handle for the PHY

    if fg.txpath.bitrate() != fg.rxpath.bitrate():
        print "WARNING: Transmit bitrate = %sb/sec, Receive bitrate = %sb/sec" % (
            eng_notation.num_to_str(fg.txpath.bitrate()),
            eng_notation.num_to_str(fg.rxpath.bitrate()))
             
    print "modulation:     %s"   % (options.modulation,)
    print "freq:           %s"      % (eng_notation.num_to_str(options.tx_freq))
    print "bitrate:        %sb/sec" % (eng_notation.num_to_str(fg.txpath.bitrate()),)
    print "samples/symbol: %3d" % (fg.txpath.samples_per_symbol(),)
    #print "interp:         %3d" % (fg.txpath.interp(),)
    #print "decim:          %3d" % (fg.rxpath.decim(),)

    fg.rxpath.set_carrier_threshold(options.carrier_threshold)
    print "Carrier sense threshold:", options.carrier_threshold, "dB"
    
    fg.start()    # Start executing the flow graph (runs in separate threads)

    tx_enabled = False
    while 1:

        rlist, wlist, xlist = select.select([sys.stdin.fileno()], [], [], 0)

        if len(rlist) != 0:
            tmp = os.read(sys.stdin.fileno(), 1024)

            if tmp.startswith("start"):
                tx_enabled = True

            if tmp.startswith("stop"):
                tx_enabled = False
                fg.txpath.stop_frame() 

        payload = ""
	for x in xrange(AUDIO_FRAMES_PER_PACKAGE):
            payload += fg.audio_rx.get_encoded_voice_packet()

        if tx_enabled:
            mac.tx(payload)

    fg.stop()     # but if it does, tell flow graph to stop.
    fg.wait()     # wait for it to finish
                

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
